from typing import Union, Callable, Optional
from datetime import datetime, timedelta, date, time as dt
from dateutil.relativedelta import relativedelta
from cy_query.cache.utils.exceptions import DateError


class DateTime(datetime):
    __datetime = ['%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f', '%Y/%m/%d %H:%M:%S.%f','%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M']
    __date = ['%Y-%m-%d', '%Y/%m/%d']
    __time = ['%H:%M:%S', '%H:%M:%S.%f','%H:%M']
    _object_none = object()

    def __new__(cls, d=_object_none, format: str = None):
        """
        将各种日期类型都转换成日期时间
        :param d:
        :param format:
        """
        source = format
        if d and format:
            d = datetime.strptime(d, format)
        elif d is cls._object_none:
            source = 'datetime'
            d = datetime.now()
        elif d is None:
            raise DateError("date can't be none")
        elif isinstance(d, str):
            for i in cls.__datetime:
                try:
                    d = datetime.strptime(d, i)
                    source = 'datetime'
                    break
                except:
                    pass
            if isinstance(d, str):
                for i in cls.__date:
                    try:
                        d = datetime.strptime(d, i)
                        source = 'date'
                        break
                    except:
                        pass
            if isinstance(d, str):
                now = datetime.now()
                for i in cls.__time:
                    try:
                        d = datetime.strptime(d, i)
                        d = d.replace(year=now.year, month=now.month, day=now.day)
                        source = 'time'
                        break
                    except:
                        pass
            if isinstance(d, str):
                raise DateError("传入方式错误")
        elif isinstance(d, datetime):
            source = 'datetime'
            d = d
        elif isinstance(d, (int, float)):
            source = 'timestamp'
            if 1000000000000 < d < 9999999999999:
                d = d / 1000
            elif 1000000000 < d < 9000000000:
                pass
            else:
                raise DateError("时间戳超出范围")
            d = datetime.fromtimestamp(d)
        elif isinstance(d, date):
            source = 'date'
            d = datetime.combine(d, datetime.min.time())
        elif isinstance(d, dt):
            source = 'time'
            d = datetime.combine(datetime.today(), d)
        elif isinstance(d, DateTime):
            d = d.date
        else:
            raise DateError("无效参数")
        obj = super().__new__(cls, year=d.year, month=d.month, day=d.day, hour=d.hour, minute=d.minute, second=d.second,
                              microsecond=d.microsecond)
        obj._source = source
        return obj

    @property
    def source(self) -> str:
        return self._source

    @source.setter
    def source(self, value):
        if value not in ['datetime', 'date', 'time', 'timestamp']:
            raise DateError("未知参数:%s" % value)
        self._source = value

    def __init__(self, d=None, format=None):
        super().__init__()

    def replace(self, year=None, month=None, day=None, hour=None,
                minute=None, second=None, microsecond=None, tzinfo=True):
        """Return a new datetime with new values for the specified fields."""
        if year is None:
            year = self.year
        if month is None:
            month = self.month
        if day is None:
            day = self.day
        if hour is None:
            hour = self.hour
        if minute is None:
            minute = self.minute
        if second is None:
            second = self.second
        if microsecond is None:
            microsecond = self.microsecond
        if tzinfo is True:
            tzinfo = self.tzinfo
        return type(self)(datetime(year, month, day, hour, minute, second, microsecond, tzinfo))

    def combine(self, t):
        if isinstance(t, dt):
            _date = datetime.combine(date=self, time=t)
        elif isinstance(t, DateTime):
            _date = datetime.combine(date=self, time=t.time())
        elif isinstance(t, str):
            _date = datetime.combine(date=self, time=dt(*[int(i) for i in t.split(':')]))
        else:
            raise DateError("未知类型")
        c = type(self)(_date)
        c.source = 'datetime'
        return c

    def to_string(self, typeof: Optional[Union[Callable, str]] = 'source') -> str:
        """
        转成字符串类型
        :param typeof:
        :return:
        """
        if typeof == 'source':
            typeof = self.source
        dic = {
            'date': self.__date[0],
            'datetime': self.__datetime[0],
            'time': "%H:%M:%S",
            'together': "%Y%m%d",
            'datetime_together': '%Y%m%d%H%M%S',
            'timestamp': self.timestamp()
        }
        res = dic.get(typeof)
        if res:
            return str(res) if isinstance(res, int) else self.strftime(res)
        if callable(typeof):
            return typeof(
                {'year': self.year, 'month': self.month, 'day': self.day, 'hour': self.hour,
                 'minute': self.minute, 'second': self.second, 'weekday': self.weekday() + 1})
        if isinstance(typeof, str):
            return self.strftime(typeof)
        raise DateError("参数错误")

    def __repr__(self):
        return "%s('%s')" % (self.__class__.__name__, self.to_string(self.source))

    def calculate_date(self, d) -> int:
        """
        对2个日期进行计算
        :param d:
        :return:
        """
        if not isinstance(d, (DateTime, datetime)):
            d = self.__class__(d)
        return (self.date() - d.date()).days

    def calculate_time(self, d) -> float:
        """
        对2个时间进行计算
        :param d:
        :return:
        """
        if not isinstance(d, (DateTime, datetime)):
            d = self.__class__(d)
        return (self - d).total_seconds()

    def __lt__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__lt__(other)

    def __le__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__le__(other)

    def __eq__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__eq__(other)

    def __ne__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__ne__(other)

    def __gt__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__gt__(other)

    def __ge__(self, other):
        if not isinstance(other, (datetime, DateTime)):
            other = self.__class__(other)
        return super().__ge__(other)

    def add_year(self, n):
        return self + relativedelta(years=n)

    def add_month(self, n):
        return self + relativedelta(months=n)

    def add_day(self, n):
        return self + timedelta(days=n)

    def add_hour(self, n):
        c = self + timedelta(hours=n)
        c.source = 'datetime'
        return c

    def add_minute(self, n):
        c = self + timedelta(minutes=n)
        c.source = 'datetime'
        return c

    def add_second(self, n):
        c = self + timedelta(seconds=n)
        c.source = 'datetime'
        return c

    def add_weekday(self, num):
        return self + timedelta(days=7 * num)

    def __add__(self, other):
        "Add a datetime and a timedelta."
        if not isinstance(other, timedelta):
            return NotImplemented
        delta = timedelta(self.toordinal(),
                          hours=self.hour,
                          minutes=self.minute,
                          seconds=self.second,
                          microseconds=self.microsecond)
        delta += other
        hour, rem = divmod(delta.seconds, 3600)
        minute, second = divmod(rem, 60)
        if 0 < delta.days <= 3652059:
            return type(self)(datetime.combine(date.fromordinal(delta.days),
                                               dt(hour, minute, second,
                                                  delta.microseconds,
                                                  tzinfo=self.tzinfo)))
        raise OverflowError("result out of range")

    def to_pydatetime(self):
        return datetime(year=self.year,hour=self.hour,month=self.month,day=self.day,minute=self.minute,second=self.second)

    def __hash__(self):
        return hash(self.to_string())

    def __str__(self):
        return super().__repr__()

    @classmethod
    def seconds_to_time(cls,seconds):
        hours,remainder = divmod(seconds,3600)
        minutes,seconds = divmod(remainder,60)
        return cls(dt(hour=int(hours),minute=int(minutes),second=int(seconds)))

    def __copy__(self):
        datetime = self.to_string(self.source)
        cls = self.__class__(datetime,
                             format=None if self.source in ['datetime', 'date', 'time', 'timestamp'] else self.source)
        return cls

    def __deepcopy__(self, memodict={}):
        return self.__copy__()
